HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import { ExternalLink } from 'lucide-react';2import type { Metadata } from 'next';3import Link from 'next/link';4import { permanentRedirect } from 'next/navigation';5import { CompareButton } from '@/components/compare/compare-button';6import { CompareTrayBar } from '@/components/compare/compare-tray-bar';7import { RelationsBlock, SourcesTable, TimelineList } from '@/components/entity/blocks';8import { buildMetadata, loadEntity } from '@/components/entity/load';9import { QUANTS } from '@/components/hardware/fit-form';10import { fmtMemory, HW_KIND_LABELS, memoryOptions } from '@/components/hardware/hardware-bits';11import { EstimateBanner, FitBreakdownList, Methodology, SourceTag } from '@/components/intelligence/bits';12import { ViewBeacon } from '@/components/layout/view-beacon';13import { Chip, EntityBadge, Estimated, OpennessBadge, StatusBadge } from '@/components/ui/badges';14import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';15import { EntityLink, QualityMark } from '@/components/ui/entity';16import { KeyValue } from '@/components/ui/key-value';17import { Container, Note, Section } from '@/components/ui/section';18import { EmptyState, Unavailable } from '@/components/ui/unavailable';19import { WatchButton } from '@/components/watchlist/watch-button';20import { api, intel, safe } from '@/lib/api';21import { fmtGb, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format';22import { PROSE_KEYS, routes, SITE_NAME, SITE_URL } from '@/lib/site';2324export const revalidate = 600;25type Params = { params: Promise<{ slug: string }>; searchParams: Promise<Record<string, string | undefined>> };26const CONTEXTS = [8192, 32768, 131072];2728export async function generateMetadata({ params }: Params): Promise<Metadata> {29 const { slug } = await params;30 const d = await safe(api.entityOfType('hardware', slug));31 if (!d || d.entity_type !== 'hardware') return { title: 'Hardware', robots: { index: false } };32 const m = buildMetadata(d);33 const a = d.attributes ?? {};34 return { ...m, title: `${d.name} — specs and which AI models fit (estimated)`, description: `${d.name}${typeof a.manufacturer === 'string' ? ` by ${a.manufacturer}` : ''}: ${fmtMemory(a.memory_gb)} memory${num(a.memory_bandwidth_gbs) !== null ? `, ${fmtInt(a.memory_bandwidth_gbs)} GB/s` : ''} as published, with sources — and the models estimated to fit at 4-bit, 8-bit and fp16. ${SITE_NAME}.` };35}3637export default async function HardwarePage({ params, searchParams }: Params) {38 const { slug } = await params;39 const sp = await searchParams;40 const d = await loadEntity('hardware', slug);41 const canonical = routes.entity(d);42 if (canonical !== `/hardware/${encodeURIComponent(slug)}`) permanentRedirect(canonical);43 const a = d.attributes ?? {};44 const quant = QUANTS.some((q) => q.value === sp.quant) ? (sp.quant as string) : '4bit';45 const ctx = num(sp.context);46 const context = ctx !== null && ctx > 0 ? Math.round(ctx) : 8192;47 const memChoice = num(sp.memory_gb);48 const options = memoryOptions(a.memory_gb);49 const fit = await safe(intel.hardwareSlugFit(d.slug, { quant, context, memory_gb: memChoice ?? undefined, limit: 120 }));50 const memoryUsed = num(fit?.inputs?.memory_gb) ?? memChoice ?? options.at(-1) ?? null;51 const self = (patch: { quant?: string; context?: number; memory_gb?: number | null }) => {52 const p = new URLSearchParams();53 const q = patch.quant ?? quant;54 const c = patch.context ?? context;55 const m = patch.memory_gb === undefined ? memChoice : patch.memory_gb;56 if (q !== '4bit') p.set('quant', q);57 if (c !== 8192) p.set('context', String(c));58 if (m !== null && m !== undefined) p.set('memory_gb', String(m));59 const s = p.toString();60 return `${canonical}${s ? `?${s}` : ''}#fit`;61 };62 const specRows = Object.keys(a)63 .filter((k) => !PROSE_KEYS.has(k))64 .map((k) => ({ key: k, raw: a[k] }));65 const items = fit?.items ?? [];66 const ld = {67 '@context': 'https://schema.org',68 '@type': 'Product',69 name: d.name,70 brand: typeof a.manufacturer === 'string' ? { '@type': 'Brand', name: a.manufacturer } : undefined,71 category: typeof a.kind === 'string' ? HW_KIND_LABELS[a.kind] ?? a.kind : 'Hardware',72 description: d.description ?? undefined,73 url: `${SITE_URL}${canonical}`,74 additionalProperty: [75 options.length ? { '@type': 'PropertyValue', name: 'Memory', value: fmtMemory(a.memory_gb) } : null,76 num(a.memory_bandwidth_gbs) !== null ? { '@type': 'PropertyValue', name: 'Memory bandwidth', value: `${fmtInt(a.memory_bandwidth_gbs)} GB/s` } : null,77 ].filter(Boolean),78 };79 const chip = (on: boolean) => `inline-flex h-9 items-center border px-2.5 text-xs font-medium ${on ? 'border-ink bg-ink text-canvas' : 'border-rule text-ink-2 hover:border-rule-strong hover:text-ink'}`;8081 return (82 <Container wide>83 <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />84 <ViewBeacon path={canonical} />85 <header className="pb-5 pt-7 md:pt-10">86 <div className="flex flex-wrap items-center gap-2 text-sm text-ink-3">87 <Link href={routes.hardware()} className="hover:text-ink">Hardware</Link> <span aria-hidden>/</span>88 <EntityBadge type={d.entity_type} />89 {typeof a.kind === 'string' && <Chip>{HW_KIND_LABELS[a.kind] ?? a.kind}</Chip>}90 <StatusBadge status={d.status} />91 </div>92 <div className="mt-2 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">93 <div className="min-w-0">94 <h1 className="display text-[28px] md:text-[40px]">{d.name}</h1>95 <p className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-ink-2">96 {typeof a.manufacturer === 'string' && <span>{a.manufacturer}</span>}97 {d.organization && (98 <Link href={routes.entity({ entity_type: 'company', slug: d.organization.slug })} className="hover:text-accent">99 {d.organization.name}100 </Link>101 )}102 {typeof a.spec_url === 'string' && (103 <a href={a.spec_url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 hover:text-accent">104 spec sheet <ExternalLink className="size-3" aria-hidden />105 </a>106 )}107 <QualityMark q={d.quality?.score} label />108 </p>109 <dl className="tnum mt-3 flex flex-wrap gap-x-6 gap-y-1 text-sm">110 <div><dt className="inline text-ink-3">Memory </dt><dd className="inline font-medium text-ink">{fmtMemory(a.memory_gb)}</dd></div>111 <div><dt className="inline text-ink-3">Bandwidth </dt><dd className="inline font-medium text-ink">{num(a.memory_bandwidth_gbs) === null ? '—' : `${fmtInt(a.memory_bandwidth_gbs)} GB/s`}</dd></div>112 <div><dt className="inline text-ink-3">TDP </dt><dd className="inline font-medium text-ink">{num(a.tdp_watts) === null ? '—' : `${fmtInt(a.tdp_watts)} W`}</dd></div>113 {Array.isArray(a.runtimes) && a.runtimes.length > 0 && <div><dt className="inline text-ink-3">Runtimes </dt><dd className="inline font-medium text-ink">{(a.runtimes as string[]).join(', ')}</dd></div>}114 </dl>115 {d.description && <p className="mt-3 max-w-2xl text-[15px] leading-relaxed text-ink-2">{d.description}</p>}116 </div>117 <div className="flex shrink-0 flex-wrap items-center gap-2">118 <CompareButton e={d} />119 <WatchButton e={d} />120 <Link href={`${routes.runLocally()}?hardware=${encodeURIComponent(d.slug)}`} className="inline-flex h-9 items-center border border-rule-strong px-2.5 text-sm font-medium text-ink hover:border-accent hover:text-accent">121 Run locally with this device →122 </Link>123 </div>124 </div>125 </header>126127 <Section eyebrow="Specifications" title="As published" lede="Every value opens its evidence (source, tier, observed time)." hairline={false}>128 <KeyValue rows={specRows} provenance={d.provenance} slug={d.slug} entity={{ name: d.name, entity_type: d.entity_type }} />129 </Section>130131 {/* ------------------------------------------------------------------------------------------------- fit */}132 <Section id="fit" eyebrow="What can this run?" title={<span className="inline-flex flex-wrap items-center gap-2">Models estimated to fit <Estimated /></span>} lede="Estimate from each model's parameter count, the chosen quantization and context — never a measurement." action={{ href: `${routes.runLocally()}?hardware=${encodeURIComponent(d.slug)}&quant=${quant}&context=${context}${memoryUsed ? `&memory_gb=${memoryUsed}` : ''}`, label: 'Open in Run locally' }}>133 <div className="mb-4 flex flex-wrap items-center gap-x-6 gap-y-3 text-xs">134 <div className="flex flex-wrap items-center gap-1.5">135 <span className="eyebrow mr-1">Quantization</span>136 {QUANTS.map((q) => (137 <Link key={q.value} href={self({ quant: q.value })} className={chip(quant === q.value)} aria-current={quant === q.value ? 'true' : undefined}>138 {q.value}139 </Link>140 ))}141 </div>142 <div className="flex flex-wrap items-center gap-1.5">143 <span className="eyebrow mr-1">Context</span>144 {CONTEXTS.map((c) => (145 <Link key={c} href={self({ context: c })} className={chip(context === c)} aria-current={context === c ? 'true' : undefined}>146 {fmtTokens(c)}147 </Link>148 ))}149 </div>150 {options.length > 1 && (151 <div className="flex flex-wrap items-center gap-1.5">152 <span className="eyebrow mr-1">Configuration</span>153 {options.map((m) => (154 <Link key={m} href={self({ memory_gb: m })} className={chip(memoryUsed === m)} aria-current={memoryUsed === m ? 'true' : undefined}>155 {m} GB156 </Link>157 ))}158 </div>159 )}160 </div>161 {!fit ? (162 <Unavailable what="Fit estimate" reason={options.length ? undefined : 'This device has no sourced memory figure — nothing can be estimated.'} />163 ) : (164 <>165 <EstimateBanner assumptions={fit.assumptions} note={fit.note ?? null} counts={fit.counts} />166 {options.length > 1 && <Note className="mt-2">This device ships in {options.length} memory configurations ({options.join(' / ')} GB). Estimates use {fmtGb(memoryUsed)} — pick another configuration above. Multiple units: use <Link href={`${routes.runLocally()}?hardware=${encodeURIComponent(d.slug)}&gpu_count=2`} className="link">Run locally</Link> (memories are summed; interconnect is not modelled).</Note>}167 {fit.runtimes?.length > 0 && <p className="mt-2 text-xs text-ink-3">Runtimes recorded for this device: <span className="mono">{fit.runtimes.join(' · ')}</span></p>}168 {items.length === 0 ? (169 <EmptyState title="No model evaluated" className="mt-4">Models without a sourced parameter count are not estimated.</EmptyState>170 ) : (171 <DataTable scroll compact className="mt-4">172 <thead>173 <tr>174 <Th>Model</Th>175 <Th num>Params</Th>176 <Th num>Est. memory</Th>177 <Th num>Headroom</Th>178 <Th>Fits</Th>179 <Th>Breakdown</Th>180 <Th className="w-24" aria-label="Compare" />181 </tr>182 </thead>183 <tbody>184 {items.length === 0 && <EmptyRow cols={7} />}185 {items.map((r) => {186 const openness = typeof r.model.attributes?.openness === 'string' ? r.model.attributes.openness : null;187 const head = num(r.headroom_gb);188 return (189 <tr key={r.model.id}>190 <Td primary>191 <span className="flex flex-wrap items-center gap-x-2 gap-y-0.5">192 <EntityLink e={r.model} />193 {openness && <OpennessBadge openness={openness} />}194 </span>195 {r.model.organization && <span className="block text-xs text-ink-3">{r.model.organization.name}</span>}196 </Td>197 <Td num label="Params" className="tnum">{fmtParams(r.parameter_count ?? r.model.attributes?.parameter_count)}</Td>198 <Td num label="Est. memory" className="tnum">{fmtGb(r.estimated_memory_gb, 1)} <SourceTag source={r.breakdown?.weights_source ?? 'estimated'} /></Td>199 <Td num label="Headroom" className={r.fits ? 'tnum text-positive' : 'tnum text-danger'}>{head === null ? '—' : `${head >= 0 ? '+' : '−'}${fmtGb(Math.abs(head), 1)}`}</Td>200 <Td label="Fits" className={r.fits ? 'font-medium text-positive' : 'text-ink-3'}>{r.fits ? '✓ fits' : '✗ too large'}</Td>201 <Td label="Breakdown" wide><FitBreakdownList fit={r} /></Td>202 <Td className="text-right"><CompareButton e={r.model} size="sm" /></Td>203 </tr>204 );205 })}206 </tbody>207 </DataTable>208 )}209 {fit.items.length >= 120 && <Note className="mt-2">First 120 models (fitting first). The full list is in <Link href={`${routes.runLocally()}?hardware=${encodeURIComponent(d.slug)}&quant=${quant}&context=${context}`} className="link">Run locally</Link>.</Note>}210 <Methodology text={fit.note ?? fit.assumptions[0]} />211 </>212 )}213 </Section>214215 {d.relations?.length ? (216 <Section eyebrow="Relations" title="In the graph" action={{ href: routes.graph(d.slug), label: 'Explore graph' }}>217 <RelationsBlock relations={d.relations} />218 </Section>219 ) : null}220 <Section eyebrow="Timeline" title="Events" action={{ href: routes.timeline({ entity: d.slug }), label: 'Full timeline' }}>221 <TimelineList events={d.timeline ?? []} slug={d.slug} />222 </Section>223 <Section eyebrow="Sources" title="Where these facts come from">224 <SourcesTable sources={d.sources ?? []} />225 </Section>226 <CompareTrayBar />227 </Container>228 );229}230